package wx;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.Date;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class WxBot {
	private String token = "";
	private String appid = "";
	private String secret = "";
	private String encodingAesKey = "";
	public WXBizMsgCrypt pc = null;
	public Encryption encryption = Encryption.None;

	private AccessTokenMaintainer atm = new AccessTokenMaintainer();
	public MessageHandler messageHandler = new MessageHandler(this);

	public WxBot(String token, String appid, String secret) {
		super();
		this.token = token;
		this.appid = appid;
		this.secret = secret;

		atm.startMaintain();
	}

	public WxBot(String token, String appid, String secret, String encodingAesKey) {
		this(token, appid, secret);
		this.encodingAesKey = encodingAesKey;
		try {
			pc = new WXBizMsgCrypt(token, this.encodingAesKey, appid);
		} catch (AesException e) {
			e.printStackTrace();
		}
		// enable encry flag
		encryption = Encryption.AES;
	}

	public boolean checkSignature(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// 消息的接收、处理、响应
		// 将请求、响应的编码均设置为UTF-8（防止中文乱码）
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		// 微信加密签名
		String signature = request.getParameter("signature");
		// 时间戳
		String timestamp = request.getParameter("timestamp");
		// 随机数
		String nonce = request.getParameter("nonce");
		// 随机字符串
		String echostr = request.getParameter("echostr");
		PrintWriter out = response.getWriter();

		// 通过检验signature对请求进行校验，若校验成功则原样返回echostr，表示接入成功，否则接入失败
		if (checkSignature(signature, timestamp, nonce)) {
			out.print(echostr);
		} else {
			System.out.println("认证失败");
		}

		out.close();
		out = null;
		return true;
	}

	public boolean checkSignature(String signature, String timestamp, String nonce) {
		return SignUtil.checkSignature(token, signature, timestamp, nonce);
	}

	/**
	 * Send a template message to user
	 * 
	 * @param toUser
	 *            (not null)
	 * @param templateID
	 *            (not null)
	 * @param url
	 * @param templateData
	 * @param preserve
	 *            (for improve)
	 * @return
	 */
	public String sendTemplateMessage(String toUser, String templateID, String url, JSONObject templateData,
			Object preserve) {
		JSONObject message = new JSONObject();
		try {
			message.put("touser", toUser).put("template_id", templateID).put("url", url).put("data", templateData);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return NetUtil.sendPost(
				"https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + getAccessToken(),
				message.toString());
	}

	public String sendMessageToUsers(List<String> openid_list, String content) {
		JSONObject json = new JSONObject();
		JSONArray touser = new JSONArray();
		JSONObject text = new JSONObject();
		for (String openid : openid_list) {
			touser.put(openid);
		}
		try {
			text.put("content", content);
			json.put("touser", touser);
			json.put("msgtype", "text");
			json.put("text", text);
		} catch (JSONException e) {
			e.printStackTrace();
		}
		return NetUtil.sendPost("https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=" + getAccessToken(),
				json.toString());
	}

	public static String makeSimpleMessage(String from, String to, String message) {
		// 回复文本消息
		TextMessage textMessage = new TextMessage();
		textMessage.setToUserName(to);
		textMessage.setFromUserName(from);
		textMessage.setCreateTime(new Date().getTime());
		textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
		// 设置文本消息的内容
		textMessage.setContent(message);
		// 将文本消息对象转换成xml
		String respXml = MessageUtil.messageToXml(textMessage);
		return respXml;
	}

	public String setMenu(String menu) {
		return NetUtil.sendPost("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + getAccessToken(), menu);
	}

	public String setMenu(JSONObject menu) {
		return setMenu(menu.toString());
	}

	public int processRequest(HttpServletRequest request, HttpServletResponse response) throws IOException {
		// 消息的接收、处理、响应
		// 将请求、响应的编码均设置为UTF-8（防止中文乱码）
		request.setCharacterEncoding("UTF-8");
		response.setCharacterEncoding("UTF-8");
		// 调用核心业务类接收消息、处理消息
		String respXml = processRequest(request);

		// 响应消息
		PrintWriter out = response.getWriter();
		out.print(respXml);
		out.close();
		return 0;
	}

	public String processRequest(HttpServletRequest request) {
		try {
			Map<String, String> requestMap = MessageUtil.parseXml(request);
			// System.out.println("。。。。开始dump。。。。");
			// NetUtil.dumpMap(requestMap);
			// decryption if need
			if (encryption == Encryption.AES) {
				// 第三方收到公众号平台发送的消息
				String msg_signature = request.getParameter("msg_signature");
				String timestamp = request.getParameter("timestamp");
				String nonce = request.getParameter("nonce");
				String result = pc.decryptMsg(msg_signature, /* 请求中的消息签名 */
						timestamp, /* 时间戳 */
						nonce, /* 无序数列 */
						requestMap.get("Encrypt"));
				// System.out.println("解密后明文: " + result);
				requestMap = MessageUtil.parseXml(new StringReader(result));
			}
			String result = CoreService.processRequest(requestMap, messageHandler);
			if (encryption == Encryption.AES) {
				String encryptMessage = pc.encryptMsg(result, new Date().getTime() + "", request.getParameter("nonce"));
				result = encryptMessage;
			}
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	public String getAccessToken() {
		return atm.accessToken;
	}

	public boolean release() {
		atm.stopMaintain();
		return true;
	}

	public MessageHandler getMessageHandler() {
		return messageHandler;
	}

	public void setMessageHandler(MessageHandler messageHandler) {
		this.messageHandler = messageHandler;
		this.messageHandler.setWxBot(this);
	}

	public String getToken() {
		return token;
	}

	public void setToken(String token) {
		this.token = token;
	}

	public String getAppid() {
		return appid;
	}

	public void setAppid(String appid) {
		this.appid = appid;
	}

	public String getSecret() {
		return secret;
	}

	public void setSecret(String secret) {
		this.secret = secret;
	}

	private class AccessTokenMaintainer {
		public final static boolean DEBUG = true;

		public String accessToken = "";
		public long lastMaintainTime = 0L;
		public long expireTime = 0L;

		private Thread maintainThread = null;
		private int signal = 0;

		public boolean startMaintain() {
			if (null != maintainThread && maintainThread.isAlive())
				return false;
			signal = 0;
			maintainThread = new Thread(maintainRunnable);
			maintainThread.start();
			return true;
		}

		public boolean stopMaintain() {
			signal = 15;
			return true;
		}

		private Runnable maintainRunnable = new Runnable() {

			@Override
			public void run() {
				for (; 15 != signal;) {
					// do some thing
					/*
					 * if (DEBUG) System.out.println("Running ...");
					 */
					if (System.currentTimeMillis() - lastMaintainTime >= (expireTime - 10) * 1000L) {
						String URI_GET_ACCESS_TOKEN = new StringBuffer(
								"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=")
								.append(appid).append("&secret=").append(secret).toString();
						String jsonStr = NetUtil.sendGet(URI_GET_ACCESS_TOKEN, null);
						try {
							JSONObject jsonObject = new JSONObject(jsonStr);
							expireTime = jsonObject.getLong("expires_in");
							accessToken = jsonObject.getString("access_token");
							lastMaintainTime = System.currentTimeMillis();
							if (DEBUG)
								System.out.println("Get Access Token : " + accessToken);
							messageHandler.onGetAccessToken(accessToken);
						} catch (JSONException e1) {
							e1.printStackTrace();
						}
					}

					try {
						Thread.sleep(1 * 1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				// clean up
				accessToken = "";
				lastMaintainTime = 0L;
				maintainThread = null;
				signal = 0;
			}
		};

	}

	public enum Encryption {
		None, Compatible, AES
	}

}
